<?php

namespace app\pay\service;

use app\common\exception\BusinessException;
use app\common\model\AppConfig;
use think\facade\Config;

/**
 * 个人小程序微信支付
 */
class WxPayService
{

    protected string $appId;

    protected array $appConfig;


    /**
     * @throws BusinessException
     */
    public function getAppConfig(): array
    {
        if (!empty($this->appConfig)) {
            return $this->appConfig;
        }
        $appConfigModel = new AppConfig();
        $this->appConfig = $appConfigModel->getByIdAndType($this->appId, 'wx_mcp_per_pay');
        if (!$this->appConfig) {
            throw new BusinessException('此应用未配置个人支付参数');
        }
        $this->appConfig['app_id'] = $appConfigModel->getValueByIdAndTypeWithName($this->appId, 'wx_mcp', 'appId');
        return $this->appConfig;
    }

    public function setAppConfig(array $appConfig): void
    {
        $this->appConfig = $appConfig;
    }


    public function getAppId(): string
    {
        return $this->appId;
    }

    public function setAppId(string $appId): WxPayService
    {
        $this->appId = $appId;
        return $this;
    }

    /**
     * buildPayData(原生小程序个人支付)
     */
    public function buildPayData(string $out_trade_no, float $total_fee, string $goods_name): array
    {
        // 支付配置
        $appConfig = $this->getAppConfig();

        // 组装数据
        $goods_name = $appConfig['mch_name'] . '-' . $goods_name;
        $payData = [
            'out_trade_no' => $out_trade_no,
            'total_fee' => $total_fee,
            'mch_id' => $appConfig['mch_id'],
            'body' => $goods_name,
        ];

        // 生成签名
        $payData['sign'] = $this->buildPaySign($payData, $appConfig['mch_key']);
        $payData['notify_url'] = $appConfig['notify_url'];
        $payData['title'] = $appConfig['mch_name'];

        // 返回
        return $payData;
    }

    /**
     * buildPayData(原生小程序个人支付)
     */
    public function newBuildPayData(string $open_id, string $out_trade_no, float $total_fee, string $goods_name): array
    {
        // 支付配置
        $appConfig = $this->getAppConfig();

        // 组装数据
        $goods_name = $appConfig['mch_name'] . '-' . $goods_name;
        $payData = [
            'out_trade_no' => $out_trade_no,
            'total_fee' => $total_fee,
            'mch_id' => $appConfig['mch_id'],
            'body' => $goods_name,
            'open_id' => $open_id,
            'app_id' => $appConfig['app_id'],
        ];

        // 生成签名
        $payData['sign'] = $this->buildPaySign($payData, $appConfig['mch_key']);
        $payData['notify_url'] = $appConfig['notify_url'];

        // 发起Http请求
        $api = $appConfig['host'] . '/api/pay/wxpay/v3/minAppPay';
        $response = curl($api, $payData, true);
        if (!$response) {
            throw new BusinessException('调用微信支付失败,请重试');
        }
        if ($response['code'] !== 0) {
            throw new BusinessException('调用微信支付失败,' . $response['msg']);
        }

        // 返回
        return $response['data'];
    }

    /**
     * buildQrPayData(原生二维码支付)
     * @param string $out_trade_no 订单号
     * @param float $total_fee 订单金额
     * @param string $goods_name 商品名称
     * @param int $type 二维码类型
     * @return mixed
     * @throws BusinessException
     */
    public function buildQrPayData(string $out_trade_no, float $total_fee, string $goods_name, int $type)
    {
        // 支付配置
        $appConfig = $this->getAppConfig();

        // 组装数据
        $goods_name = $appConfig['mch_name'] . '-' . $goods_name;
        $payData = [
            'out_trade_no' => $out_trade_no,
            'total_fee' => $total_fee,
            'mch_id' => $appConfig['mch_id'],
            'body' => $goods_name,
        ];

        // 生成签名
        $payData['sign'] = $this->buildPaySign($payData, $appConfig['mch_key']);
        $payData['type'] = $type;
        $payData['notify_url'] = $appConfig['notify_url'];

        // 发起Http请求
        $api = $appConfig['host'] . '/api/pay/wxpay/nativePay';
        $response = curl($api, $payData, true);
        if (!$response) {
            throw new BusinessException('调用微信支付失败,请重试');
        }
        if ($response['code'] !== 0) {
            throw new BusinessException('调用微信支付失败,' . $response['msg']);
        }

        // 返回
        return $response['data'];
    }

    /**
     * buildCashierPayData(原生聚合支付)
     * @param string $out_trade_no 订单号
     * @param float $total_fee 订单金额
     * @param string $goods_name 商品名称
     * @return mixed
     * @throws BusinessException
     */
    public function buildCashierPayData(string $out_trade_no, float $total_fee, string $goods_name)
    {
        // 支付配置
        $appConfig = $this->getAppConfig();

        // 组装数据
        $goods_name = $appConfig['mch_name'] . '-' . $goods_name;
        $payData = [
            'out_trade_no' => $out_trade_no,
            'total_fee' => $total_fee,
            'mch_id' => $appConfig['mch_id'],
            'body' => $goods_name,
        ];

        // 生成签名
        $payData['sign'] = $this->buildPaySign($payData, $appConfig['mch_key']);
        $payData['notify_url'] = $appConfig['notify_url'];

        // 发起Http请求
        $api = $appConfig['host'] . '/api/pay/wxpay/cashierPay';
        $response = curl($api, $payData, true);
        if (!$response) {
            throw new BusinessException('调用微信支付失败,请重试');
        }
        if ($response['code'] !== 0) {
            throw new BusinessException('调用微信支付失败,' . $response['msg']);
        }

        // 返回
        return $response['data'];
    }

    /**
     * 生成跳转Url
     * @param $wxUrl
     * @return string
     */
    public function buildJumpUrl($wxUrl): string
    {
        $appConfig = Config::get('app');
        return $appConfig['app_host'] . '/pay/jump/toWxPay?wxUrl=' . urlencode($wxUrl);
    }

    /**
     * buildPaySign
     * @param array $data
     * @param $key
     * @return string
     */
    private function buildPaySign(array $data, $key): string
    {
        if (isset($data['sign'])) {
            unset($data['sign']);
        }
        ksort($data);
        return strtoupper(md5(urldecode(http_build_query($data)) . '&key=' . $key));
    }

    /**
     * checkSign
     * @param string $sourceSign
     * @param array $data
     * @return bool
     * @throws BusinessException
     */
    public function checkSign(string $sourceSign, array $data): bool
    {
        $appConfig = $this->getAppConfig();
        $buildSign = $this->buildPaySign($data, $appConfig['mch_key']);
        return $sourceSign == $buildSign;
    }

}